package centerberg.engine;

import java.awt.event.KeyListener;
import java.util.Collection;
import java.util.HashSet;

/**
 * The base class for the entire engine. This is what game programmers
 * interface via.
 * 
 * @author Andreas Svanberg, Linus Centerström
 */
public class Engine
{
    private Collection<GameObject> gameObjects = new HashSet<GameObject>();
    private Collection<GameObject> toAdd = new HashSet<GameObject>();
    private Collection<GameObject> toRemove = new HashSet<GameObject>();
    
    private boolean run;
    private boolean paused;
    
    public final Render render;
    public final PhysicsEngine physics;
    public final SoundEngine sound;
    public final ResourceManager resourceManager;
    
    private Engine()
    {
        render = FrameRender.getInstance();
        physics = new PhysicsEngine();
        resourceManager = new ResourceManager();
        sound = new SoundEngine();
    }
    /**
     * Runs the game
     */
    public void run() {
        long previousTime = System.currentTimeMillis();
        double elapsed = 0;
        
        run = true;
        paused = false;
        
        gameLoop: while(run)
        {
            elapsed = System.currentTimeMillis() - previousTime;

            while (paused)
            {
                try { Thread.sleep(1); }
                catch (Exception e) { }
            }

            previousTime = System.currentTimeMillis();

            physics.runSimulation(elapsed);

            Scene nextScene = render.createScene();
            for(GameObject gameObj : gameObjects) {
                gameObj.tick(elapsed);
                // breaks the loop right away if any of the tick functions calls for it to break
                if(!run)
                    break gameLoop;
                if(gameObj instanceof Drawable)
                    nextScene.add((Drawable)gameObj);
            }
            render.render(nextScene);
            
            // add new objects to the loop
            // to prevent concurrent modification exception in game loop
            synchronized (this) {
                gameObjects.addAll(toAdd);
                gameObjects.removeAll(toRemove);
                toAdd.clear();
                toRemove.clear();
            }
            
            try { Thread.sleep(1); }
            catch (Exception e) { }
        }
        
        gameObjects.clear();
        physics.clearWorld();
    }
    
    public synchronized void add(GameObject obj)
    {
        toAdd.add(obj);
    }
    
    public synchronized void remove(GameObject obj)
    {
        toRemove.add(obj);
    }
    
    /**
     * Stops the current gameLoop
     */
    public void stop() {
        run = false;
    }
    /**
     * Stops the current loop
     * and closes the application
     */
    public void exit() {
        run = false;
        System.exit(0);
    }
    
    public static Engine getInstance()
    {
        return EngineHolder.INSTANCE;
    }
    
    public void init()
    {
        render.init();
    }
    
    public void pause()
    {
        paused = true;
    }
    
    public void resume()
    {
        paused = false;
    }
    
    public void addKeyListener(KeyListener l)
    {
        render.getRenderingComponent().addKeyListener(l);
    }
    
    private static final class EngineHolder {
        private static final Engine INSTANCE = new Engine();
    }
}